package wiki.xsx.jdbc;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @Descript 增强JdbcTemplate查询方法,提供一系列获取实体类的方法
 * @Author xsx
 * @Date 2017/11/11
 */
public class JdbcTemplatePlus extends JdbcTemplate {
    /**
     * 根据实体类型获取全部实体类(单表)
     * @param entityClass 实体类型
     * @param <T> 具体类型
     * @return 返回实体类列表
     */
    public <T> List<T> getEntityAll(Class<T> entityClass){
        return this.getEntityAll(entityClass, null);
    }

    /**
     * 根据实体类型与排序条件获取全部实体类(单表)
     * @param entityClass 实体类型
     * @param sort 排序条件
     * @param <T> 具体类型
     * @return 返回实体类列表
     */
    public <T> List<T> getEntityAll(Class<T> entityClass, Sort sort){
        return this.getEntitiesByCondition(entityClass, null, sort);
    }

    /**
     * 根据实体类型与条件列表获取全部实体类(单表)
     * @param entityClass 实体类型
     * @param conditions 条件列表
     * @param <T> 具体类型
     * @return 返回实体类列表
     */
    public <T> List<T> getEntitiesByCondition(Class<T> entityClass, List<Condition> conditions){
        return this.getEntitiesByCondition(entityClass, conditions, null);
    }

    /**
     * 根据实体类型、条件列表与排序条件获取全部实体类(单表)
     * @param entityClass 实体类型
     * @param conditions 条件列表
     * @param sort 排序条件
     * @param <T> 具体类型
     * @return 返回实体类列表
     */
    public <T> List<T> getEntitiesByCondition(Class<T> entityClass, List<Condition> conditions, Sort sort){
        List<Object> args = new ArrayList<>();
        StringBuilder sql = this.getSelectSqlAndArgs(entityClass, "*", conditions, args, sort);
        return this.query(sql.toString(), args.toArray(), new BeanPropertyRowMapper<>(entityClass));
    }

    /**
     * 根据实体类型与条件数组获取全部实体类(单表)
     * @param entityClass 实体类型
     * @param conditions 条件数组
     * @param <T> 具体类型
     * @return 返回实体类列表
     */
    public <T> List<T> getEntitiesByCondition(Class<T> entityClass, Condition... conditions){
        return this.getEntitiesByCondition(entityClass, null, conditions);
    }

    /**
     * 根据实体类型、排序条件与条件数组获取全部实体类(单表)
     * @param entityClass 实体类型
     * @param sort 排序条件
     * @param conditions 条件数组
     * @param <T> 具体类型
     * @return 返回实体类列表
     */
    public <T> List<T> getEntitiesByCondition(Class<T> entityClass, Sort sort, Condition... conditions){
        return this.getEntitiesByCondition(entityClass, Arrays.asList(conditions), sort);
    }

    /**
     * 根据实体类型与主键获取实体类(单表)
     * @param entityClass 实体类型
     * @param primaryKey 主键字段名
     * @param id 主键值
     * @param <T> 具体类型
     * @return 返回实体类
     */
    public <T> T getEntity(Class<T> entityClass, String primaryKey, Object id){
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT * FROM ").append(entityClass.getSimpleName())
            .append(" WHERE ").append(primaryKey).append("=?");
        return this.queryForObject(sql.toString(), new BeanPropertyRowMapper<>(entityClass), id);
    }

    /**
     * 根据实体类型与主键获取实体类(单表,默认主键名为id)
     * @param entityClass 实体类型
     * @param id 主键值
     * @param <T> 具体类型
     * @return 返回实体类
     */
    public <T> T getEntity(Class<T> entityClass, Object id){
        T t;
        try {
            t = this.getEntity(entityClass, "id", id);
        }catch (EmptyResultDataAccessException e){
            this.logger.debug(e.getMessage());
            try {
                t = entityClass.newInstance();
            }catch (InstantiationException|IllegalAccessException ex){
                this.logger.debug(ex.getMessage());
                t = null;
            }
        }
        return t;
    }

    /**
     * 根据实体类型与条件列表获取实体类(单表)
     * @param entityClass 实体类型
     * @param conditions 条件列表
     * @param <T> 具体类型
     * @return 返回实体类
     */
    public <T> T getEntityByCondition(Class<T> entityClass, List<Condition> conditions){
        return this.getEntityByCondition(entityClass, conditions, null);
    }

    /**
     * 根据实体类型、条件列表与排序条件获取实体类(单表)
     * @param entityClass 实体类型
     * @param conditions 条件列表
     * @param sort 排序条件
     * @param <T> 具体类型
     * @return 返回实体类
     */
    public <T> T getEntityByCondition(Class<T> entityClass, List<Condition> conditions, Sort sort){
        List<Object> args = new ArrayList<>();
        StringBuilder sql = this.getSelectSqlAndArgs(entityClass, "*", conditions, args, sort);
        return this.queryForObject(sql.toString(), args.toArray(), new BeanPropertyRowMapper<>(entityClass));
    }

    /**
     * 根据实体类型与条件数组获取实体类(单表)
     * @param entityClass 实体类型
     * @param conditions 条件数组
     * @param <T> 具体类型
     * @return 返回实体类
     */
    public <T> T getEntityByCondition(Class<T> entityClass, Condition... conditions){
        return this.getEntityByCondition(entityClass, null, conditions);
    }

    /**
     * 根据实体类型、排序条件与条件数组获取实体类(单表)
     * @param entityClass 实体类型
     * @param sort 排序条件
     * @param conditions 条件数组
     * @param <T> 具体类型
     * @return 返回实体类
     */
    public <T> T getEntityByCondition(Class<T> entityClass, Sort sort, Condition... conditions){
        return this.getEntityByCondition(entityClass, Arrays.asList(conditions), sort);
    }

    /**
     * 根据实体类型获取数据库中总记录数(单表)
     * @param entityClass 实体类型
     * @param <T> 具体类型
     * @return 返回实体类总数量
     */
    public <T> long getEntityCount(Class<T> entityClass){
        return this.getEntityCountByCondition(entityClass);
    }

    /**
     * 根据实体类型与条件列表获取数据库中总记录数(单表)
     * @param entityClass 实体类型
     * @param conditions 条件列表
     * @param <T> 具体类型
     * @return 返回实体类总数量
     */
    public <T> long getEntityCountByCondition(Class<T> entityClass, List<Condition> conditions){
        StringBuilder sql = this.getSelectSqlAndArgs(entityClass, "COUNT(*)", conditions, new ArrayList<>(), null);
        return this.queryForObject(sql.toString(), Long.class);
    }

    /**
     * 根据实体类型与条件数组获取数据库中总记录数(单表)
     * @param entityClass 实体类型
     * @param conditions 条件数组
     * @param <T> 具体类型
     * @return 返回实体类总数量
     */
    public <T> long getEntityCountByCondition(Class<T> entityClass, Condition... conditions){
        return this.getEntityCountByCondition(entityClass, Arrays.asList(conditions));
    }

    /**
     * 保存实体对象(单表)
     * @param t 实体对象
     * @param <T> 具体类型
     * @return 返回自增主键
     */
    public <T> Serializable saveEntity(T t){
        List<Object> args = new ArrayList<>();
        StringBuilder sql = this.getInsertSqlAndArgs(t, args);
        KeyHolder keyHolder = new GeneratedKeyHolder();
        this.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
            for (int i=0;i<args.size();i++){
                ps.setObject(i+1, args.get(i));
            }
            return  ps;
        }, keyHolder);
        return keyHolder.getKey().longValue();
    }

    /**
     * 批量保存实体对象(单表)
     * @param entityList 实体对象列表
     * @param <T> 具体类型
     * @return 返回自增主键集合
     */
    public <T> List<Serializable> batchSaveEntity(List<T> entityList){
        List<Serializable> ids = new ArrayList<>();
        for (T t : entityList){
            ids.add(this.saveEntity(t));
        }
        return ids;
    }

    /**
     * 批量保存实体对象(单表)
     * @param ts 实体对象数组
     * @param <T> 具体类型
     */
    public <T> void batchSaveEntity(T... ts){
        this.batchSaveEntity(Arrays.asList(ts));
    }

    /**
     * 根据主键字段更新实体对象(单表)
     * @param t 实体对象
     * @param primaryKey 主键字段名
     * @param <T> 具体类型
     */
    public <T> void updateEntity(T t, String primaryKey){
        List<Object> args = new ArrayList<>();
        StringBuilder sql = this.getUpdateSqlAndArgs(t, primaryKey, args);
        this.update(sql.toString(), args.toArray());
    }

    /**
     * 更新实体对象(单表,默认主键名为id)
     * @param t 实体对象
     * @param <T> 具体类型
     */
    public <T> void updateEntity(T t){
        this.updateEntity(t, "id");
    }

    /**
     * 根据主键字段批量更新实体对象(单表)
     * @param entityList 实体对象列表
     * @param primaryKey 主键字段名
     * @param <T> 具体类型
     */
    public <T> void batchUpdateEntities(List<T> entityList, String primaryKey){
        for (T t : entityList){
            this.updateEntity(t, primaryKey);
        }
    }

    /**
     * 批量更新实体对象(单表)
     * @param entityList 实体对象列表
     * @param <T> 具体对象
     */
    public <T> void batchUpdateEntities(List<T> entityList){
        for (T t : entityList){
            this.updateEntity(t);
        }
    }

    /**
     * 根据条件数组及实体对象模版(单表)
     * @param template 实体对象模版(主键值为null)
     * @param conditions 条件数组
     * @param <T> 具体类型
     */
    public <T> void batchUpdateEntities(T template, Condition... conditions){
        this.batchUpdateEntities(template, Arrays.asList(conditions));
    }

    /**
     * 根据条件列表及实体对象模版(单表)
     * @param template 实体对象模版(主键值为null)
     * @param conditions 条件列表
     * @param <T> 具体类型
     */
    public <T> void batchUpdateEntities(T template, List<Condition> conditions){
        List<Object> args = new ArrayList<>();
        StringBuilder sql = this.getUpdateSqlAndArgs(template, null, args);
        this.addConditions(sql, conditions, args);
        this.update(sql.toString(), args.toArray());
    }

    /**
     * 根据实体对象模版删除单个或多个实体对象(单表)
     * @param template 实体对象模版
     * @param <T> 具体类型
     */
    public <T> void deleteEntity(T template){
        List<Object> args = new ArrayList<>();
        StringBuilder sql = this.getDeleteSqlAndArgs(template, null, args);
        this.update(sql.toString(), args.toArray());
    }

    /**
     * 根据主键字段名及主键值数组删除单个或多个实体对象(单表)
     * @param entityClass 实体类型
     * @param primaryKey 主键字段名
     * @param ids 主键值数组
     * @param <T> 具体类型
     */
    public <T> void deleteEntity(Class<T> entityClass, String primaryKey, Object... ids){
        this.deleteEntity(entityClass, primaryKey, Arrays.asList(ids));
    }

    /**
     * 根据主键字段名及主键值列表删除单个或多个实体对象(单表)
     * @param entityClass 实体类型
     * @param primaryKey 主键字段名
     * @param ids 主键值列表
     * @param <T> 具体类型
     */
    public <T> void deleteEntity(Class<T> entityClass, String primaryKey, List<Object> ids){
        List<Object[]> args = new ArrayList<>();
        for (Object id : ids){
            Object[] idArray = {id};
            args.add(idArray);
        }
        StringBuilder sql = this.getDeleteSqlAndArgs(entityClass, primaryKey, null);
        this.batchUpdate(sql.toString(), args);
    }

    /**
     * 根据主键值数组删除单个或多个实体对象(单表,默认主键名为id)
     * @param entityClass 实体类型
     * @param id 主键值数组
     * @param <T> 具体类型
     */
    public <T> void deleteEntity(Class<T> entityClass, Object... id){
        this.deleteEntity(entityClass, "id", id);
    }

    /**
     * 根据主键值列表删除单个或多个实体对象(单表,默认主键名为id)
     * @param entityClass 实体类型
     * @param id 主键值列表
     * @param <T> 具体类型
     */
    public <T> void deleteEntity(Class<T> entityClass, List<Object> id){
        this.deleteEntity(entityClass, "id", id);
    }

    /**
     * 批量删除实体对象(单表)
     * @param entityLit 实体对象列表
     * @param <T> 具体类型
     */
    public <T> void batchDeleteEntities(List<T> entityLit){
        for (T t : entityLit){
            this.deleteEntity(t);
        }
    }

    /**
     * 根据实体类型及条件列表删除实体对象(单表)
     * @param entityClass 实体类型
     * @param conditions 条件列表
     * @param <T> 具体类型
     */
    public <T> void deleteEntityByCondition(Class<T> entityClass, List<Condition> conditions){
        List<Object> args = new ArrayList<>();
        StringBuilder sql = this.getDeleteSqlAndArgs(entityClass, null, args);
        this.addConditions(sql, conditions, args);
        this.update(sql.toString(), args.toArray());
    }

    /**
     * 根据实体类型及条件数组删除实体对象(单表)
     * @param entityClass 实体类型
     * @param conditions 条件数组
     * @param <T> 具体类型
     */
    public <T> void deleteEntityByCondition(Class<T> entityClass, Condition... conditions){
        this.deleteEntityByCondition(entityClass, Arrays.asList(conditions));
    }


    /**
     * 根据实体类型、查询内容、查询条件、排序条件获取查询SQL(单表)
     * @param entityClass 实体类型
     * @param query 查询内容
     * @param conditions 条件列表
     * @param args 查询参数
     * @param sort 排序条件
     * @param <T> 具体类型
     * @return 返回查询SQL
     */
    private <T> StringBuilder getSelectSqlAndArgs(Class<T> entityClass, String query, List<Condition> conditions, List<Object> args, Sort sort){
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT ").append(query).append(" FROM ").append(entityClass.getSimpleName())
                .append(" WHERE 1=1 ");
        this.addConditions(sql, conditions, args);
        if (sort!=null){
            sql.append(" ORDER BY ");
            Map<String, Sort.SortType> sortMap = sort.getSort();
            for (Map.Entry<String, Sort.SortType> entry : sortMap.entrySet()){
                sql.append(entry.getKey()).append(" ").append(entry.getValue().name()).append(",");
            }
            sql = sql.deleteCharAt(sql.length()-1);
        }
        return sql;
    }

    /**
     * 增加sql条件
     * @param sql sql语句
     * @param conditions 条件列表
     * @param args 参数列表
     */
    private void addConditions(StringBuilder sql, List<Condition> conditions, List<Object> args){
        if (conditions!=null){
            for (Condition condition : conditions){
                sql.append(" ").append(condition.getJoinKey()).append(" ")
                        .append(condition.getFieldName()).append(" ");
                if ("IN".equalsIgnoreCase(condition.getSymbol())){
                    sql.append("IN(").append(StringUtil.getPlaceholder(StringUtil.getPlaceholderSize(condition.getValue().toString()))).append(")");
                    args.addAll(StringUtil.getArgs(condition.getValue().toString()));
                }else if("BETWEEN,AND".equalsIgnoreCase(condition.getSymbol())){
                    sql.append(" BETWEEN ? AND ? ");
                    args.addAll(Arrays.asList(condition.getValue().toString().split(",")));
                }else{
                    sql.append(condition.getSymbol()).append(" ");
                    if (condition.getValue()!=null){
                        sql.append("?");
                        args.add(condition.getValue());
                    }
                }
            }
        }
    }

    /**
     * 根据实体对象获取插入sql语句及参数列表
     * @param t 实体对象
     * @param args 参数列表
     * @param <T> 具体类型
     * @return 返回插入sql语句
     */
    private <T> StringBuilder getInsertSqlAndArgs(T t, List<Object> args){
        Class<? extends Object> entityClass = t.getClass();
        StringBuilder sql = new StringBuilder();
        StringBuilder argsBuild = new StringBuilder();
        sql.append("INSERT INTO ").append(entityClass.getSimpleName()).append(" (");
        Map<String, Object> data = StringUtil.getFieldNameAndValue(t);
        for (Map.Entry<String, Object> entry : data.entrySet()){
            sql.append(entry.getKey()).append(",");
            args.add(entry.getValue());
            argsBuild.append("?,");
        }
        sql = sql.deleteCharAt(sql.length()-1);
        sql.append(") VALUES(").append(argsBuild.deleteCharAt(argsBuild.length()-1)).append(")");
        return sql;
    }

    /**
     * 根据实体对象获取更新sql语句及参数列表
     * @param t 实体对象
     * @param primaryKey 主键字段名
     * @param args 参数列表
     * @param <T> 具体类型
     * @return 返回更新sql语句
     */
    private <T> StringBuilder getUpdateSqlAndArgs(T t, String primaryKey, List<Object> args){
        Class<? extends Object> entityClass = t.getClass();
        StringBuilder sql = new StringBuilder();
        sql.append("UPDATE ").append(entityClass.getSimpleName()).append(" SET ");
        Map<String, Object> data = StringUtil.getFieldNameAndValue(t);
        for (Map.Entry<String, Object> entry : data.entrySet()){
            if (entry.getValue()!=null){
                sql.append(entry.getKey()).append("=?,");
                args.add(entry.getValue());
            }
        }
        sql = sql.deleteCharAt(sql.length()-1);
        sql.append(" WHERE 1=1 ");
        if (primaryKey!=null){
            sql.append(" AND ").append(primaryKey).append("=?");
            args.add(data.get(primaryKey));
        }
        return sql;
    }

    /**
     * 根据实体对象获取删除sql语句及参数
     * @param t 实体对象
     * @param args 主键字段名
     * @param <T> 具体类型
     * @return 返回删除sql语句
     */
    private <T> StringBuilder getDeleteSqlAndArgs(T t, String primaryKey, List<Object> args){
        Class<? extends Object> entityClass = t.getClass();
        StringBuilder sql = new StringBuilder();
        sql.append("DELETE FROM ").append(entityClass.getSimpleName()).append(" WHERE 1=1 ");
        Map<String, Object> data = StringUtil.getFieldNameAndValue(t);
        for (Map.Entry<String, Object> entry : data.entrySet()){
            if (entry.getValue()!=null){
                sql.append(" AND ").append(entry.getKey()).append("=? ");
                args.add(entry.getValue());
            }
        }
        if (primaryKey!=null){
            sql.append(" AND ").append(primaryKey).append("=?");
        }
        sql = sql.deleteCharAt(sql.length()-1);
        return sql;
    }

    /**
     * 根据实体对象获取批量操作参数列表
     * @param t 实体对象
     * @param <T> 具体类型
     * @return 返回参加列表
     */
    private <T> List<Object> getBatchArgs(T t){
        List<Object> args = new ArrayList<>();
        Map<String, Object> data = StringUtil.getFieldNameAndValue(t);
        for (Map.Entry<String, Object> entry : data.entrySet()){
            args.add(entry.getValue());
        }
        return args;
    }
}
